Hire Voip Development

Table of Content

Curious About Superior Communication?

Partner with Our Skilled Developers!

How to Stream FreeSWITCH Call Audio to an AI Model With mod_audio_fork?

Streaming FreeSWITCH call audio to an AI model with mod_audio_fork

📝 Blog Summary

Connecting telephony channels to streaming speech models requires real-time media transport. With FreeSWITCH mod_audio_fork, developers attach a media bug to an active session, turning live audio into full-duplex WebSocket frames for transcription, LLMs, and speech synthesis.
This blog covers compiling the module on modern releases, setting up production dialplans, comparing it against mod_audio_stream, and preventing the latency bottlenecks that break conversational turn-taking.

To stream FreeSWITCH call audio to an AI model with FreeSWITCH mod_audio_fork, attach a media bug to the active channel session to capture raw Linear16 (L16) audio, stream those PCM frames in real time over WebSockets to an external speech-to-text pipeline, and return synthesized audio chunks over the same duplex socket for caller playback.

Building an interactive voice AI bot over standard telephony introduces architectural friction. Telephony networks operate in discrete 20ms G.711 or Opus packet intervals. Streaming AI models (such as whisper.cpp, faster-whisper, or Deepgram) expect clean, raw PCM streams delivered over persistent network sockets. Writing audio frames to local disk files (.wav) creates disk I/O bottlenecks and introduces hundreds of milliseconds of latency, making natural turn-taking impossible.

mod_audio_fork solves this problem by intercepting audio directly inside the FreeSWITCH core switch session, bypassing disk storage entirely.

end to end bidirectional voice ai architecture

What Is mod_audio_fork in FreeSWITCH, and How Does It Work?

mod_audio_fork is an open-source FreeSWITCH endpoint application module that taps an active channel’s audio stream using the FreeSWITCH media_bug API. It streams uncompressed Linear16 (L16) audio frames over WebSockets (WS/WSS) to a remote server without storing temporary audio files on local disk.

Originally developed in the Drachtio ecosystem, the module runs in three operational phases:

  1. Attaching the Media Bug: The module hooks into the session via switch_core_media_bug_add(). It reads raw audio from either the caller leg (mono), both sides combined (mixed), or isolated tracks across separate stereo channels (stereo).
  2. Resampling and Packet Framing: FreeSWITCH automatically handles sample rate conversion. If the leg enters as 8kHz G.711 u-law, the media bug can upsample or downsample on the fly to deliver standard 8kHz or 16kHz 16-bit linear PCM.
  3. Full-Duplex Transport: Audio leaves FreeSWITCH as raw binary WebSocket messages. Simultaneously, the remote AI server can return audio packets back down the exact same WebSocket connection, instructing the module to play the audio directly into the caller’s ear.

mod_audio_fork vs mod_audio_stream: Which Should You Use?

While mod_audio_fork established the standard for WebSocket media tapping in FreeSWITCH, modern production voice AI stacks increasingly standardize on mod_audio_stream.

The original mod_audio_fork repository has seen limited updates over recent years, causing build breakages on contemporary Linux distributions and modern FreeSWITCH codebases. By contrast, mod_audio_stream is actively maintained, introduces native thread-safe connection pooling, supports granular WebSocket connection heartbeats, and compiles cleanly against current dependencies.

Use this mod_audio_fork vs. mod_audio_stream comparison matrix to select the right module for your environment:

Feature / dimension mod_audio_fork mod_audio_stream
Active maintenance Community mirrors only; original repo archived or unmaintained Actively maintained by the amigniter community
Tested FreeSWITCH versions Native on 1.6 / 1.8; requires patches for 1.10+ Native on FreeSWITCH 1.10.x and 1.11.x releases
Audio format Raw Linear16 (L16) PCM Raw Linear16 (L16) PCM
Transport WS / WSS (WebSockets) WS / WSS with configurable TLS options
Bidirectional audio playback Supported Supported (native base64 or raw binary injection)
Dynamic mid-call metadata Supported via uuid_audio_fork send_text Supported via uuid_audio_stream send_text
Recommended deployment Existing clusters running legacy pipelines New real-time AI voice builds
Audio dropouts or media bug crashes stalling your voice bot?

AI Voicebot vs Transcription Service: Choosing the Right Audio Streaming Mode

To configure FreeSWITCH mod_audio_fork correctly, you need to match the audio mix type to your application goals: use bidirectional mono at 16kHz for interactive voice AI bots, and unidirectional stereo at 8kHz or 16kHz for passive post-call or real-time transcription.

  • For Real-Time AI Voicebots: You only need the caller’s audio (mono) streamed to your Speech-to-Text (STT) model. Crucially, you must keep the duplex WebSocket open so your Text-to-Speech (TTS) synthesizer can return binary audio frames back into FreeSWITCH for caller playback. Never use mixed mode for a voicebot since it combines caller and bot audio into a single stream, causing the STT model to transcribe the bot’s own voice and triggering false interruptions (barge-in loops).
  • For Real-Time Agent Assist & Compliance Transcription: You need dual-channel (stereo) audio. Channel 0 captures the caller, and Channel 1 captures the agent. Streaming isolated tracks allows speech recognition engines to transcribe both speakers simultaneously without crosstalk or channel bleed.

Also learn how to build an AI voice agent with FreeSWITCH and WebRTC.

How to Install and Compile FreeSWITCH mod_audio_fork on Modern FreeSWITCH Releases?

To install and compile mod_audio_fork on FreeSWITCH 1.10+, clone the repository, install development headers, point CMake to the decoupled FreeSWITCH libraries (libks and sofia-sip), compile the shared object, and load the module via fs_cli.

Most public tutorials fail because FreeSWITCH decoupled core libraries into external packages. If you run a standard make, the linker fails to locate header files.

Here is the build sequence for Debian 11/12:

# 1. Install prerequisites and development headers
sudo apt-get update && sudo apt-get install -y \
    build-essential cmake pkg-config libwebsockets-dev \
    libssl-dev zlib1g-dev mod_audio_fork

# 2. Clone the module repository
git clone https://github.com/mdslaney/mod_audio_fork.git
cd mod_audio_fork

# 3. Ensure PKG_CONFIG_PATH points to FreeSWITCH libraries
export PKG_CONFIG_PATH=/usr/lib/pkgconfig:/usr/local/FreeSWITCH/lib/pkgconfig

# 4. Configure and build via CMake
mkdir build && cd build
cmake -DCMAKE_BUILD_TYPE=Release ..
make -j$(nproc)

# 5. Copy the compiled shared object into the FreeSWITCH module directory
sudo cp mod_audio_fork.so /usr/lib/FreeSWITCH/mod/
# (or /usr/local/FreeSWITCH/mod/ depending on your installation prefix)

Once copied, tell FreeSWITCH to load the module at runtime via fs_cli:

fs_cli -x "load mod_audio_fork"

To persist the module across server reboots, add the load directive to your /etc/FreeSWITCH/autoload_configs/modules.conf.xml file:

<configuration name="modules.conf" description="Modules">
  <modules>
    <!-- Core and application modules -->
    <load module="mod_audio_fork"/>
  </modules>
</configuration>

FreeSWITCH Module Development Dialplan Configuration and API Syntax

To stream audio via the dialplan, answer the call, set subprotocol headers, execute the uuid_audio_fork start command with your target WebSocket URL, and park the channel to keep media flowing.

The command syntax requires five core arguments:

uuid_audio_fork  start    [metadata]
  • <uuid>: The unique channel identifier generated by FreeSWITCH.
  • <wss-url>: Your target WebSocket server address (ws:// or wss://).
  • <mix-type>: Audio capture channel mode (mono for caller only, mixed for both legs summed, or stereo for isolated tracks).
  • <sampling-rate>: Output sample rate (8k or 16k).
  • [metadata]: An optional JSON-formatted string sent as the initial text frame immediately upon handshake.

Production XML dialplan example:

<include>
  <context name="public"&gt;
    <extension name="voice_ai_ingress">
      <condition field="destination_number" expression="^5000$">
        <action application="answer"/>

        <!-- Optional subprotocol header required by some proxy servers -->
        <action application="set" data="MOD_AUDIO_FORK_SUBPROTOCOL_NAME=audio.custom.org"/>

     <!-- Start media bug: fork to WebSocket server in 16k mono -->
<action application="eval" data="${uuid_audio_fork(${uuid} start ws://10.0.0.50:8765 
mono 16k {\"caller_id\":\"${caller_id_number}\",\"call_id\":\"${uuid}\"})}"/>

        <!-- Keep session alive while AI model processes speech -->
        <action application="park"/>
      </condition>
    </extension>
  </context>
</include>

Sending Mid-Call Metadata and Handling Teardown

Telephony isn’t static. When a caller presses a DTMF key or an external database dip resolves, you can pass structured JSON strings mid-call over the existing socket without interrupting media transport:

fs_cli -x "uuid_audio_fork  send_text {\"event\":\"dtmf\",\"digit\":\"1\"}"

When the conversation finishes, gracefully detach the media bug and close the socket:

fs_cli -x "uuid_audio_fork  stop"

How to Connect FreeSWITCH mod_audio_fork to End-to-End Voice AI Pipeline? (FreeSWITCH to STT, LLM, and Audio Playback)

Connecting FreeSWITCH mod_audio_fork to an end-to-end voice AI pipeline requires an asynchronous WebSocket server that buffers 20ms Linear16 frames for streaming STT, passes resolved transcripts to an LLM, and streams synthesized TTS audio back down the WebSocket for playback to the caller.

Full Duplex Real time voice ai pipeline (flow)

Here is a working asynchronous Python server using websockets that ingests audio, extracts session metadata, and demonstrates how to return synthesized audio back to the caller:

import asyncio
import json
import websockets

async def voice_ai_bridge(websocket):
    print("[WS] FreeSWITCH connected to audio socket.")
    session_data = None

    try:
        async for message in websocket:
            # 1. Handle incoming text frames (Initial metadata or mid-call DTMF)
            if isinstance(message, str):
                session_data = json.loads(message)
                print(f"[METADATA] Initialized session: {session_data}")
                
            # 2. Handle binary frames (Raw Linear16 PCM audio)
            elif isinstance(message, bytes):
                # 16kHz audio: 16,000 samples/sec * 2 bytes/sample * 
0.02s = 640 bytes per 20ms chunk
                raw_pcm_chunk = message
                
                # Push raw_pcm_chunk to your streaming STT engine (e.g., faster-whisper)
                # user_text = await stt_service.transcribe(raw_pcm_chunk)
                
                # When your LLM generates a response and your TTS synthesizes audio,
                # send playback instructions back down this EXACT WebSocket:
                # response_payload = {
                #     "type": "playAudio",
                #     "audioContentType": "raw",
                #     "sampleRate": 16000,
                #     "audioContent": base64_audio_data
                # }
                # await websocket.send(json.dumps(response_payload))
                
    except websockets.exceptions.ConnectionClosed:
        print("[WS] FreeSWITCH session disconnected.")
    except Exception as e:
        print(f"[ERROR] Exception in audio bridge: {e}")

async def main():
    server = await websockets.serve(voice_ai_bridge, "0.0.0.0", 8765)
    print("Voice AI WebSocket Bridge running on port 8765...")
    await server.wait_closed()

if __name__ == "__main__":
    asyncio.run(main())

Production Latency and Concurrency Pitfalls (Tuning FreeSWITCH Media Bugs)

Moving from a local sandbox to 200 concurrent calls will expose specific low-level bottlenecks inside FreeSWITCH:

1. Speex Resampler CPU Overload

If incoming calls arrive over G.711 (8kHz) and your STT model requires 16kHz, FreeSWITCH runs software resampling on every active audio packet inside the media bug callback. Across hundreds of concurrent channels, this burns substantial CPU.

  • How to Fix: Whenever possible, negotiate wideband codecs (like G.722 or Opus) with your carrier upstream, or benchmark whether your STT engine can ingest native 8kHz audio directly without upsampling.

2. Service Worker Thread Contention

By default, the module processes outbound WebSocket frames across a shared thread pool. Under high network traffic, socket latency can block the media bug loop.

  • How to Fix: Tune this via environmental configuration before launching FreeSWITCH:
# Allocate between 1 and 5 dedicated network service threads
export MOD_AUDIO_FORK_SERVICE_THREADS=4

Allocating dedicated service threads ensures socket latency on one call does not stall audio frame processing on adjacent channels.

3. TCP Window Clamping and Buffer Overruns

If your Python or Node.js server blocks for even 50ms while evaluating an AI model, the TCP receive window clamps down. When TCP buffers fill up, FreeSWITCH’s media bug queue overflows, causing audio dropouts and audible stuttering.

  • How to Fix: Decouple audio ingestion from inference. Let your WebSocket handler push raw audio packets immediately into an in-memory queue (like Redis or an asyncio.Queue), leaving your network thread free to ingest the next 20ms frame without delay.

💡 Expert Tip

Never force FreeSWITCH to handle core SIP transcoding on the same channel where you attach mod_audio_fork.

If a call requires converting incoming G.711 to Opus before forking L16 audio over WebSockets, the core switch thread will experience jitter under CPU spikes.

Terminate your carrier media as clean G.711 or native Opus at an edge Session Border Controller (SBC), and pass uniform, pre-transcoded audio to your FreeSWITCH media nodes.

Need seasoned VoIP engineers to build a custom C-module or scale your voice AI core?

Custom FreeSWITCH Module Development (When Stock Plugins Fall Short)

While stock tools like mod_audio_fork and mod_audio_stream work well for standard WebSocket routing, high-volume production platforms often hit limitations:

  • Direct Shared-Memory Transport: Passing audio frames through local shared-memory (shm) circular buffers directly to GPU workers, bypassing the TCP/WebSocket stack entirely.
  • In-Module Opus Decoding: Decoding Opus packets directly into linear PCM inside the media bug callback to eliminate core switch transcoding overhead.
  • Mutual TLS (mTLS) Security: Enforcing hardware-backed cryptographic certificates between carrier media servers and internal inference clusters.

When your platform requires advanced FreeSWITCH module development, custom C/C++ media plane optimizations, or carrier-grade voice AI scaling, our senior VoIP engineering team is here to assist.

Whether you need to debug complex media bugs or architect low-latency voice cores, you can hire a dedicated FreeSWITCH engineer with deep telecom experience to build and harden your infrastructure.

FAQs

What is mod_audio_fork in FreeSWITCH, and what does it do?

mod_audio_fork is an endpoint application module that attaches a non-blocking media bug to an active FreeSWITCH channel. It captures raw Linear16 (L16) audio in real time and streams it over WebSockets to an external server for speech-to-text transcription and AI processing, bypassing the need to store audio files on local disk.

mod_audio_fork vs mod_audio_stream: which should you use?

For new voice AI implementations, use mod_audio_stream. It is actively maintained, supports modern FreeSWITCH 1.10+ releases out of the box, and includes native thread-safe connection pooling. Use mod_audio_fork only if you are maintaining an existing legacy cluster already standardized on its schema.

Is mod_audio_fork still maintained, and why do tutorials break?

The original mod_audio_fork repository is largely unmaintained. Most online tutorials break because older build scripts target FreeSWITCH 1.6 and 1.8. Modern FreeSWITCH 1.10+ decoupled core dependencies like libks and sofia-sip into standalone external libraries, which requires updated CMake and pkg-config paths during compilation.

How to stream FreeSWITCH call audio to an AI model or WebSocket server?

Load the module in FreeSWITCH, then execute uuid_audio_fork start via the XML dialplan or Event Socket Library (ESL). This attaches a media bug to the channel and pipes binary PCM frames over a persistent WebSocket connection directly to your AI backend.

How to send call metadata to the WebSocket server mid-call?

Execute the command uuid_audio_fork send_text via fs_cli or ESL. The module serializes the payload and sends it over the active WebSocket connection as a text frame without interrupting the binary audio stream.

Tags
Picture of Sagar Malam
Sagar Malam
Sagar is a seasoned IT strategist with over a decade of experience crafting and executing complex VoIP projects. With a deep understanding of Apache Kafka, Jira, Figma (Software), UCaaS, and the Internet Protocol Suite (TCP/IP), he drives innovation and delivers exceptional solutions. Off duty, Sagar explores the frontiers of tech because innovation never sleeps, and neither does he.
Scroll to Top